/*
 * ConstructionParams.java
 *
 * Created on Dec 21, 2011
 *
 * Copyright (c) 2011 Artur Rataj.
 *
 * This code is distributed under the terms of the GNU Library
 * General Public License, either version 3 of the license or, at
 * your option, any later version.
 */

package pl.gliwice.iitis.spatialnn.network.construct;

import java.util.*;

import pl.gliwice.iitis.graphutils.Point;

/**
 * Construction parameters of a given neuron, to be used by construction
 * objects like <code>Wood</code>.<br>
 * 
 * The parameters are default, the constructor can modify them when searching
 * for an optimal architecture.
 * 
 * @author Artur Rataj
 */
public class ConstructionParams implements Cloneable {
    /**
     * Distribution of inputs' number. The outcomes are clipped to the
     * range of possible inputs of a neuron.
     */
    public Gaussian inNum;
    /**
     * Distribution of inputs' distance, mean: 0.05 for tiny, 0.5 for large,
     * sigma: 0.1 for tiny, 1.0 for large. Clipped to non--negative.
     */
    public Gaussian inDist;
    /**
     * An absolute value of a standard deviation of inputs' angle in radians, 0.1 for tiny, 1.0 for large.
     * The outcomes are clipped to &gt;-Pi, Pi&lt;, where Pi/2 points upwards to the input membranes. Expectation
     * is fixed at Pi/2 for a non--negative value of this field, or for the other values, at Pi/4 and 3Pi/4 for the right and
     * left inputs, respectively.
     */
    public double inAngleSigma;
    /**
     * Minimum distance, at which input candidates to different components,
     * having these properties, are clustered.
     */
    public double minClusterDist;
    
    /**
     * Creates a new instance of <code>ConstructionParams</code>.
     * 
     * @param inNum copied to the field <code>inNum<code>
     * @param inDist copied to the field <code>inDist<code>
     * @param angleSigma copied to the field <code>angleSigma<code>
     * @param minClusterDist copied to the field <code>minClusterDist<code>
     */
    public ConstructionParams(Gaussian inNum, Gaussian inDist,
            double inAngleSigma, double minClusterDist) {
        this.inNum = inNum;
        this.inDist = inDist;
        this.inAngleSigma = inAngleSigma;
        this.minClusterDist = minClusterDist;
    }
   /**
     * Clips <code>v</code> to &lt;<code>min</code>, <code>max</code>&gt;.
     * 
     * @param v input value
     * @param min minimum
     * @param max maximum
     * @return clipped <code>v</code>
     */
    double clip(double v, double min, double max) {
        if(v < min)
            return min;
        else if(v > max)
            return max;
        else
            return v;
    }
    /**
     * Draws the number of inputs.
     * 
     * @param min minimum number of inputs
     * @param max maximum number of inputs, -1 for a lack of maximum value
     * @return a number of inputs, drawn using <code>inNumMean</code> and
     * <code>inNumSigma</code>, and then clipped
     */
    public int drawInNum(int min, int max) {
        return (int)Math.round(
                clip(inNum.draw(), min,
                    (max == -1 ? Integer.MAX_VALUE : max)));
    }
    /**
     * Draws a primary location of an input.
     * 
     * @param base base location, relative to which the output location is drawn;
     * the output's y coordinate is never smaller than the base's y coordinate
     * @param forwardAngle mean azimuth, at which the inputs are located
     * @param inputType type of the input to draw
     * @param customInputSpread diffference to the forward angle of
     * left/right inputs, <code>Double.NaN</code> for the default of
     * Pi/4
     * @return a random location, drawn using <code>inDistSigma</code> and
     * <code>inAngleSigma</code>
     */
    public Point drawInLoc(Point base, double forwardAngle, InputType type,
            double customInputSpread) {
        double dist = clip(inDist.draw(), 0.0, Double.MAX_VALUE);
        double middle;
        double inputSpread;
        if(!Double.isNaN(customInputSpread))
            inputSpread = customInputSpread;
        else
            inputSpread = Math.PI/4.0;
        switch(type) {
            case ANY:
                middle = forwardAngle;
                if(inAngleSigma < 0.0)
                    throw new RuntimeException("invalid deviation for this input type");
                break;
                
            case LEFT:
                middle = forwardAngle + inputSpread;
                if(inAngleSigma >= 0.0)
                    throw new RuntimeException("invalid deviation for this input type");
                break;
                
            case RIGHT:
                middle = forwardAngle - inputSpread;
                if(inAngleSigma >= 0.0)
                    throw new RuntimeException("invalid deviation for this input type");
                break;

            default:
                throw new RuntimeException("unknown type of input");
        }
        double angle = middle + Gaussian.normal(0.0, Math.abs(inAngleSigma));
        angle = clip(angle,
                forwardAngle - Math.PI/2.0,
                forwardAngle + Math.PI/2.0);
        Point out = new Point(
                base.x + dist*Math.cos(angle),
                base.y + dist*Math.sin(angle));
        return out;
    }
    @Override
    public ConstructionParams clone() {
        try {
            ConstructionParams copy = (ConstructionParams)super.clone();
            copy.inDist = new Gaussian(inDist);
            copy.inNum = new Gaussian(inNum);
            return copy;
        } catch(CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}
